package ru.voronoy.thefifteen.gameplay;

import java.awt.Point;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.swing.tree.DefaultMutableTreeNode;

import ru.voronoy.thefifteen.config.BoneConf;
import ru.voronoy.thefifteen.entity.Bone;

public class Solution {
	private static Set<Map<String, Point>> bonesStatement = new HashSet<Map<String, Point>>();
	private static boolean isGameOver = false;

	public static Map<String, Bone> Solve(Map<String, Bone> bones) {
		Map<String, Point> currentStatement = getBonesState(bones);

		DefaultMutableTreeNode root = new DefaultMutableTreeNode(
				currentStatement);
		Fill(root, bones);
		// JTree tree = new JTree(root);
		System.out.println(root.getDepth());

		return bones;
	}

	private static void Fill(DefaultMutableTreeNode node,
			Map<String, Bone> bones) {
		Bone currentBone = null;
		// DefaultMutableTreeNode child = new DefaultMutableTreeNode();

		// if (Game.getInstance().IsOver(bones)){
		// return;
		// }
		if (Game.getInstance().IsOver(bones)) {
			isGameOver = true;
		}

		if (node.isLeaf()) {
			if (Game.getInstance().getBoneUpByEmpty(bones) != null) {
				currentBone = Game.getInstance().getBoneUpByEmpty(bones);
				Game.getInstance().MoveClickedBone(bones, currentBone);
				// child = new DefaultMutableTreeNode(getBonesState(bones));
				node.add(new DefaultMutableTreeNode(getBonesState(bones)));
				if (Game.getInstance().IsOver(bones)) {
					isGameOver = true;
				}
				Game.getInstance().MoveClickedBone(bones, currentBone);
			}
			if (Game.getInstance().getBoneLeftByEmpty(bones) != null) {
				currentBone = Game.getInstance().getBoneLeftByEmpty(bones);
				Game.getInstance().MoveClickedBone(bones, currentBone);
				// child = new DefaultMutableTreeNode(getBonesState(bones));
				node.add(new DefaultMutableTreeNode(getBonesState(bones)));
				if (Game.getInstance().IsOver(bones)) {
					isGameOver = true;
				}
				Game.getInstance().MoveClickedBone(bones, currentBone);
			}
			if (Game.getInstance().getBoneRightByEmpty(bones) != null) {
				currentBone = Game.getInstance().getBoneRightByEmpty(bones);
				Game.getInstance().MoveClickedBone(bones, currentBone);
				// child = new DefaultMutableTreeNode(getBonesState(bones));
				node.add(new DefaultMutableTreeNode(getBonesState(bones)));
				if (Game.getInstance().IsOver(bones)) {
					isGameOver = true;
				}
				Game.getInstance().MoveClickedBone(bones, currentBone);
			}
			if (Game.getInstance().getBoneDownByEmpty(bones) != null) {
				currentBone = Game.getInstance().getBoneDownByEmpty(bones);
				Game.getInstance().MoveClickedBone(bones, currentBone);
				// child = new DefaultMutableTreeNode(getBonesState(bones));
				node.add(new DefaultMutableTreeNode(getBonesState(bones)));
				if (Game.getInstance().IsOver(bones)) {
					isGameOver = true;
				}
				Game.getInstance().MoveClickedBone(bones, currentBone);
			}
		}
		// else{
		if (!isGameOver) {
			for (int i = 0; i < node.getChildCount(); i++) {
				Fill((DefaultMutableTreeNode) node.getChildAt(i), bones);
			}
		}
		// }
	}

	public static Map<String, Bone> Hint(Map<String, Bone> bones) {
		if (Game.getInstance().IsOver(bones)) {
			Solution.bonesStatement.clear();
			return bones;
		}

		Map<Bone, Integer> possibleBones = new HashMap<Bone, Integer>();
		Bone currentBone = null;
		// int bonesStatementSize = getBonesStatementSize();

		if (Game.getInstance().getBoneUpByEmpty(bones) != null) {
			currentBone = Game.getInstance().getBoneUpByEmpty(bones);
			Game.getInstance().MoveClickedBone(bones, currentBone);
			if (!Solution.IsBonesStatementContainsState(bones)) {
				possibleBones.put(currentBone,
						Solution.getManhattanDistanceSum(bones));
			}
			Game.getInstance().MoveClickedBone(bones, currentBone);
		}
		if (Game.getInstance().getBoneLeftByEmpty(bones) != null) {
			currentBone = Game.getInstance().getBoneLeftByEmpty(bones);
			Game.getInstance().MoveClickedBone(bones, currentBone);
			if (!Solution.IsBonesStatementContainsState(bones)) {
				possibleBones.put(currentBone,
						Solution.getManhattanDistanceSum(bones));
			}
			Game.getInstance().MoveClickedBone(bones, currentBone);
		}
		if (Game.getInstance().getBoneRightByEmpty(bones) != null) {
			currentBone = Game.getInstance().getBoneRightByEmpty(bones);
			Game.getInstance().MoveClickedBone(bones, currentBone);
			if (!Solution.IsBonesStatementContainsState(bones)) {
				possibleBones.put(currentBone,
						Solution.getManhattanDistanceSum(bones));
			}
			Game.getInstance().MoveClickedBone(bones, currentBone);
		}
		if (Game.getInstance().getBoneDownByEmpty(bones) != null) {
			currentBone = Game.getInstance().getBoneDownByEmpty(bones);
			Game.getInstance().MoveClickedBone(bones, currentBone);
			if (!Solution.IsBonesStatementContainsState(bones)) {
				possibleBones.put(currentBone,
						Solution.getManhattanDistanceSum(bones));
			}
			Game.getInstance().MoveClickedBone(bones, currentBone);
		}

		int minMD = Integer.MAX_VALUE;
		for (Map.Entry<Bone, Integer> entry : possibleBones.entrySet()) {
			if (entry.getValue() <= minMD) {
				// if (!entry.getKey().equals(yetbone)){
				minMD = entry.getValue();
				currentBone = entry.getKey();
				// yetbone = currentbone;
				// }
			}
		}

		Game.getInstance().MoveClickedBone(bones, currentBone);
		Solution.getBonesState(bones);
		possibleBones.clear();

		return bones;
	}

	public static Map<String, Point> getBonesState(Map<String, Bone> bones) {
		Map<String, Point> statement = new HashMap<String, Point>();
		for (Map.Entry<String, Bone> entry : bones.entrySet()) {
			statement.put(entry.getKey(), entry.getValue().getLocation());
		}
		bonesStatement.add(statement);
		return statement;
	}

	public static int getBonesStatementSize() {
		return bonesStatement.size();
	}

	public static boolean IsBonesStatementContainsState(Map<String, Bone> bones) {
		Map<String, Point> statement = new HashMap<String, Point>();
		for (Map.Entry<String, Bone> entry : bones.entrySet()) {
			statement.put(entry.getKey(), entry.getValue().getLocation());
		}
		return bonesStatement.contains(statement);
	}

	public static int getManhattanDistance(Map<String, Bone> bones,
			String boneKey) {
		if (bones.get(boneKey).getLocation()
				.equals(BoneConf.getInstance().CoordinatesSet().get(boneKey))) {
			return 0;
		} else {
			// текущие координаты "костяшки"
			int x1 = bones.get(boneKey).getX();
			int y1 = bones.get(boneKey).getY();
	
			// ожидаемые в финале координаты "костяшки"
			int x2 = (int) BoneConf.getInstance().CoordinatesSet().get(boneKey)
					.getX();
			int y2 = (int) BoneConf.getInstance().CoordinatesSet().get(boneKey)
					.getY();
	
			return (int) (Math.abs(x2 - x1) + Math.abs(y2 - y1))
					/ BoneConf.getInstance().SPACE;
		}
	
	}

	public static int getManhattanDistanceSum(Map<String, Bone> bones) {
		int result = 0;
		for (Map.Entry<String, Bone> entry : bones.entrySet()) {
			result += getManhattanDistance(bones, entry.getKey());
		}
		return result;
	}

}
